package SystemC;

/******************************************************************************************************************
* File:SMMonitor.java
* Course: 17655
* Project: Assignment A3
* Copyright: Copyright (c) 2013 Carnegie Mellon University
* Versions:
*	1.0 March 2013 - luisrocha
*
* Description:
*
* This class monitors the registered components and alerts if they are not responding.
* 
*
* Parameters: IP address of the event manager (on command line). If blank, it is assumed that the event manager is
* on the local machine.
*
******************************************************************************************************************/
import EventPackage.*;
import InstrumentationPackage.*;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

class SMMonitor extends Thread
{	    
    private EventManagerInterface em = null;        // Interface object to the event manager
    private String EvtMgrIP = null;			// Event Manager IP address
    boolean Registered = true;			// Signifies that this class is registered with an event manager.
    MessageWindow mw = null;			// This is the message window

    private Map<Long,Device> deviceList = new HashMap();         // to hold list of registered devices

    public SMMonitor()
    {		              
        // event manager is on the local system
        try
        {
                // Here we create an event manager interface object. This assumes
                // that the event manager is on the local machine
                em = new EventManagerInterface();
        }
        catch (Exception e)
        {
                System.out.println("SMMonitor::Error instantiating event manager interface: " + e);
                Registered = false;
        } // catch
    } //Constructor

    public SMMonitor( String EvmIpAddress )
    {
        // event manager is not on the local system
        EvtMgrIP = EvmIpAddress;
        try
        {
                // Here we create an event manager interface object. This assumes
                // that the event manager is NOT on the local machine
                em = new EventManagerInterface( EvtMgrIP );
        }
        catch (Exception e)
        {
                System.out.println("SMMonitor::Error instantiating event manager interface: " + e);
                Registered = false;
        } // catch
    } // Constructor

    @Override
    public void run()
    {
        final String ELEMENT_TYPE = "SMC";              // identification for heartbeat event
        Event Evt = null;				// Event object
        EventQueue eq = null;                           // Message Queue
        int EvtId = 0;					// User specified event ID
        int	Delay = 1000;				// The loop delay (1 second)
        boolean Done = false;                           // Loop termination flag

        if (em != null)
        {
                // Now we create the SM status and message panel
                mw = new MessageWindow("Service Management Console", 0.5f, 0);
                mw.WriteMessage( "Registered with the event manager." );
            try
            {
                mw.WriteMessage("   Participant id: " + em.GetMyId() );
                mw.WriteMessage("   Registration Time: " + em.GetRegistrationTime() );

                //create initial heartbeat event
                Event evt = new Event( (int) 98, ELEMENT_TYPE);                    
                em.SendEvent( evt );
                mw.WriteMessage(evt.GetEventId() + "   Initial Heartbeat: " + evt.GetMessage() + " " + evt.GetSenderId());

            } // try
            catch (Exception e)
            {
                System.out.println("Error:: " + e);
            } // catch

        /********************************************************************
        ** Here we start the main simulation loop
        *********************************************************************/

            while ( !Done )
            {
                // Here we get our event queue from the event manager
                try
                {
                        eq = em.GetEventQueue();                                    
                } // try
                catch( Exception e )
                {
                        mw.WriteMessage("Error getting event queue::" + e );
                } // catch

                // If there are messages in the queue, we read through them.
                // We are looking for EventIDs = 98. Event IDs of 98 are the heartbeat
                // events produced by each device registered in the system
                // we create a list of devices and output the first heartbeat event of a device
                // to the list
                // we then monitor the list of devices for unresponsive ones (more than 5s without a hearbeat)

                int qlen = eq.GetSize();

                for ( int i = 0; i < qlen; i++ )
                {
                    Evt = eq.GetEvent();

                    if ( Evt.GetEventId() == 98 ) // Heartbeat event
                    {
                        try
                        {                                       
                            Device tempDevice = null;
                            //check if the device that sent the heartbeat does not exist in map
                            if ( this.getDeviceList().containsKey(Evt.GetSenderId()) )
                            {
                                //get existing device in the list and set new hearbeat time
                                tempDevice = this.getDeviceList().get(Evt.GetSenderId());
                                tempDevice.setDeviceLastHeartbeat(System.currentTimeMillis()); 
                                //mw.WriteMessage("Updated heartbeat: " + tempDevice.getDeviceID() + 
                                //        " " + tempDevice.getDeviceType());
                            }
                            else
                            {
                                //add new device to list with hearbeat set to current system time
                                tempDevice = new Device (Evt.GetSenderId(), Evt.GetMessage());
                                this.getDeviceList().put(Evt.GetSenderId(), tempDevice);
                                mw.WriteMessage("Created Device: " + tempDevice.getDeviceID() + 
                                   " " + tempDevice.getDeviceType());
                            } //end if

                        } // try
                        catch( Exception e )
                        {
                            mw.WriteMessage("Error reading heartbeat: " + e);
                        } // catch
                    } // if


                    // If the event ID == 99 then this is a signal that the simulation
                    // is to end. At this point, the loop termination flag is set to
                    // true and this process unregisters from the event manager.

                    if ( Evt.GetEventId() == 99 )
                    {
                        Done = true;
                        try
                        {
                             em.UnRegister();
                        }// try
                        catch (Exception e)
                        {
                            mw.WriteMessage("Error unregistering: " + e);
                        } // catch

                        mw.WriteMessage( "\n\nSimulation Stopped. \n");
                    } // if
                } // for

                // This delay slows down the sample rate to Delay milliseconds
                try
                {
                    // create a hearbeat event
                    Event evt = new Event( (int) 98, ELEMENT_TYPE);
                    em.SendEvent( evt );
                    //mw.WriteMessage(evt.GetEventId() + "   Heartbeat: " + evt.GetMessage() + " " + evt.GetSenderId());

                    //check for unresponsive devices
                    Map<Long,Device> unresponsiveDeviceList = new HashMap();
                    unresponsiveDeviceList = this.getUnresponsiveDeviceList();

                    if ( !unresponsiveDeviceList.isEmpty() )
                    {
                        for (Device i : unresponsiveDeviceList.values())
                        {                                     
                            
                            Calendar TimeStamp = Calendar.getInstance();
                            //only display warnings for 5 seconds
                            if ( i.getDeviceLastHeartbeat() + 10000 > TimeStamp.getTimeInMillis() )
                            {    
                                TimeStamp.setTimeInMillis(i.getDeviceLastHeartbeat());
                                SimpleDateFormat TimeStampFormat = new SimpleDateFormat("yyyy MM dd::hh:mm:ss:SSS");
                                String TimeString = TimeStampFormat.format(TimeStamp.getTime());

                                mw.WriteMessage( " UNRESPONSIVE DEVICE: " + i.getDeviceType() +
                                    " " + i.getDeviceID() + 
                                    " Last Hb: " + TimeString);
                            } // end if 
                        }  //for                                        
                    } // if

                    //sleep
                    Thread.sleep( Delay );

                } // try
                catch( Exception e )
                {
                     System.out.println( "Sleep or hearbeat error:: " + e );
                } // catch

             } // while

        } else {
                System.out.println("Unable to register with the event manager.\n\n" );
        } // if

    } // main

    /***************************************************************************
    * CONCRETE METHOD:: IsRegistered
    * Purpose: This method returns the registered status
    *
    * Arguments: none
    *
    * Returns: boolean true if registered, false if not registered
    *
    * Exceptions: None
    *
    ***************************************************************************/

    public boolean IsRegistered()
    {
            return( Registered );

    } // IsRegistered

    /***************************************************************************
    * CONCRETE METHOD:: Halt
    * Purpose: This method posts an event that stops the environmental control
    *		   system.
    *
    * Arguments: none
    *
    * Returns: none
    *
    * Exceptions: Posting to event manager exception
    *
    ***************************************************************************/

    public void Halt()
    {
            mw.WriteMessage( "***HALT MESSAGE RECEIVED - SHUTTING DOWN SYSTEM***" );

            // Here we create the stop event.

            Event evt;

            evt = new Event( (int) 99, "XXX" );

            // Here we send the event to the event manager.

            try
            {
                    em.SendEvent( evt );

            } // try

            catch (Exception e)
            {
                    System.out.println("Error sending halt message:: " + e);

            } // catch

    } // Halt

    /**
     * @return the deviceList
     */
    public Map<Long,Device> getDeviceList() {
        return deviceList;
    }

    /**
     * This method returns the list of devices that are unresponsive. This means that the last
     * heartbeat occurred more than 5000 milliseconds ( or the value of hearbeat delay) from the current time
     * 
     * @return the unresponsiveDeviceList
     */

    public Map<Long,Device> getUnresponsiveDeviceList() 
    {
        final long HEARTBEAT_DELAY = 5000; //milliseconds between current time and last heartbeat to sound alarm
        final Calendar getTime = Calendar.getInstance();
        Map<Long,Device> unresponsiveDeviceList = new HashMap();

        //run all the list of devices and check the last heartbeat time
        for (Device i : this.getDeviceList().values())
        {

            // mark to the return list the ones where the last heartbeat is over the delay
            if ( (i.getDeviceLastHeartbeat() + HEARTBEAT_DELAY) < getTime.getTimeInMillis() )
            {
                unresponsiveDeviceList.put(i.getDeviceID(), i);

            } // end if

        } //end if    
        return unresponsiveDeviceList;

    } // getUnresponsiveDeviceList
     
} // ECSMonitor